﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using System.Diagnostics;

namespace TubeTest
{
    class TubeObject
    {
        private VertexPositionNormalTexture[] shapeVertices;
        private int shapeTriangles;
        private VertexBuffer shapeBuffer;
        public Texture2D shapeTexture;

        private List<TubeNode> nodes = new List<TubeNode>();

        public TubeObject()
        { }

        public void addNode(TubeNode node)
        {
            nodes.Add(node);
        }

        public void addNode(Vector3 pos)
        {
            TubeNode n = new TubeNode(pos);
            nodes.Add(n);
        }

        public void addNode(float X, float Y, float Z)
        {
            TubeNode n = new TubeNode(X, Y, Z);
            nodes.Add(n);
        }

        public void randomizeLast(float m)
        {
            if(nodes.Count > 0)
                nodes[nodes.Count - 1].displacementNoise(m);
        }

        public TubeNode last() { return nodes[nodes.Count - 1]; }

        Vector3 cubicInterpolate(Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3, float mu)
        {

            Vector3 a0, a1, a2, a3;
            float mu2;

            mu2 = mu * mu;
            a0 = y3 - y2 - y0 + y1; //p
            a1 = y0 - y1 - a0;
            a2 = y2 - y0;
            a3 = y1;

            return (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3);
        }

        float cubicInterpolateF(float y0, float y1, float y2, float y3, float mu)
        {

            float a0, a1, a2, a3;
            float mu2;

            mu2 = mu * mu;
            a0 = y3 - y2 - y0 + y1; //p
            a1 = y0 - y1 - a0;
            a2 = y2 - y0;
            a3 = y1;

            return (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3);
        }

        public void subdivide(int steps)
        {
            List<TubeNode> sdiv = new List<TubeNode>();
            sdiv.Add(nodes[0]);
            for (int i = 1; i < nodes.Count - 2; ++i)
            {
                sdiv.Add(nodes[i]);

                if ((nodes[i].Position - nodes[i + 1].Position).Length() > 3.0f)
                {
                    for (int n = 0; n < steps; ++n)
                    {
                        float coef = (float)(n + 1) / (float)(steps + 1);
                        //TubeNode node = new TubeNode(Vector3.SmoothStep(nodes[i].Position, nodes[i + 1].Position, coef));
                        TubeNode node = new TubeNode(cubicInterpolate(nodes[i - 1].Position, nodes[i].Position, nodes[i + 1].Position, nodes[i + 2].Position, coef));
                        //node.radius = MathHelper.SmoothStep(nodes[i].radius, nodes[i + 1].radius, coef);
                        node.radius = cubicInterpolateF(nodes[i - 1].radius, nodes[i].radius, nodes[i + 1].radius, nodes[i + 2].radius, coef);
                        sdiv.Add(node);
                    }
                }
            }

            sdiv.Add(nodes[nodes.Count - 2]);
            sdiv.Add(nodes[nodes.Count - 1]);

            nodes.Clear();
            nodes = sdiv;
        }

        public void copyDisplacements(TubeObject another)
        {
            if (another == null) return;
            if (another.nodes.Count < nodes.Count) return;

            for (int i = 0; i < nodes.Count; ++i)
            {
                for (int n = 0; n < TubeNode.DisplacementSamples; ++n)
                {
                    nodes[i].displacement[n] = another.nodes[i].displacement[n];
                }
            }
        }

        public void setRadiusToAll(float rad)
        {
            foreach (TubeNode n in nodes)
                n.radius = rad;
        }

        public void randomizeAll(float rad)
        {
            foreach (TubeNode n in nodes)
                n.displacementNoise(rad);
        }

        public void recalculateGeometry(bool inverted)
        {
            if(nodes.Count < 2) return;

            nodes[0].orient(null, nodes[1]);

            for (int i = 1; i < nodes.Count - 1; ++i)
                nodes[i].orient(nodes[i - 1], nodes[i + 1]);

            nodes[nodes.Count - 1].orient(nodes[nodes.Count - 2], null);

            for (int i = 0; i < nodes.Count; ++i)
                nodes[i].updateVertices(inverted);

            shapeTriangles = (nodes.Count - 1) * TubeNode.DisplacementSamples * 2;
            shapeVertices = new VertexPositionNormalTexture[shapeTriangles * 3];

            Trace.WriteLine("Triangles : " + shapeTriangles);
            Trace.WriteLine("Vertices : " + (shapeTriangles * 3));

            for (int i = 0; i < nodes.Count - 1; ++i)
            {
                for (int n = 0; n < TubeNode.DisplacementSamples; ++n)
                {
                    int x = i * TubeNode.DisplacementSamples * 2 * 3 + n * 6;
                    int m = (n + 1) % TubeNode.DisplacementSamples;

                    if (inverted)
                    {
                        shapeVertices[x + 0] = nodes[i].Vertices[m];
                        shapeVertices[x + 1] = nodes[i + 1].Vertices[n];
                        shapeVertices[x + 2] = nodes[i].Vertices[n];

                        shapeVertices[x + 3] = nodes[i].Vertices[m];
                        shapeVertices[x + 4] = nodes[i + 1].Vertices[m];
                        shapeVertices[x + 5] = nodes[i + 1].Vertices[n];
                    }
                    else
                    {
                        shapeVertices[x + 0] = nodes[i].Vertices[n];
                        shapeVertices[x + 1] = nodes[i + 1].Vertices[n];
                        shapeVertices[x + 2] = nodes[i].Vertices[m];

                        shapeVertices[x + 3] = nodes[i + 1].Vertices[n];
                        shapeVertices[x + 4] = nodes[i + 1].Vertices[m];
                        shapeVertices[x + 5] = nodes[i].Vertices[m];
                    }

                    if (n == TubeNode.DisplacementSamples - 1)
                    {
                        for (int z = 0; z < 6; ++z)
                            if (shapeVertices[x + z].TextureCoordinate.X == 0.0f)
                                shapeVertices[x + z].TextureCoordinate.X = 1.0f;
                    }
                }
            }

            shapeBuffer = null;
        }

        public void RenderShape(GraphicsDevice device)
        {
            //BuildShape();

            if (shapeBuffer == null)
            {
                shapeBuffer = new VertexBuffer(device, typeof(VertexPositionNormalTexture), shapeVertices.Length, BufferUsage.WriteOnly);
                shapeBuffer.SetData(shapeVertices);
            }

            device.SetVertexBuffer(shapeBuffer);
            device.DrawPrimitives(PrimitiveType.TriangleList, 0, shapeTriangles);
        }
    }
}
